# SSN - actors
# Copyright (c) 2009, jonoerik
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
# conditions are met:
#
#    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
#	disclaimer.
#    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
#	disclaimer in the documentation and/or other materials provided with the distribution.
#    * The names of contributors may not be used to endorse or promote products derived from this software without specific
#	prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

#actors
#pc, npc and monster code

#library imports
from sys import path as syspath
syspath.append('.//libtcod-1.4.1')
import libtcodpy as libtcod
import numpy

import map
from util import *

class actor_handler:
    '''a class to handle the actors on the current map'''
    
    def __init__(self):
        self.actor_list = []
    
    def add_crowds(self, g, count):
        '''Adds count NPCs to the map
        this saves time spent finding random positions, as it uses a single avaliable_tiles
        array for all the npcs'''
        
        #find a random position
        avaliable_tiles = alltiles(g.map.width, g.map.height)
        npc_count = 0
        while npc_count < count:
            if len(avaliable_tiles) > 0: #if tiles are left
                #get a random tile
                cindex = rand(0, len(avaliable_tiles) - 1)
                ctile = avaliable_tiles[cindex]
                #if the tile is open
                if g.map.land[ctile[1], ctile[0]].walkable and not g.actors.tile_occupied(g, ctile):
                    self.add_npc(g, ctile)
                    npc_count += 1
                else:
                    #if tile not open, remove it from the list
                    avaliable_tiles = avaliable_tiles[:cindex] + avaliable_tiles[cindex + 1:]
            else:
                #if no walkable tiles exist on the map, just pick a random tile
                new_pos_x = rand(0, g.map.width - 1)
                new_pos_y = rand(0, g.map.height - 1)
                self.add_npc(g, (new_pos_x, new_pos_y))
                npc_count += 1
    
    def add_npc(self, g, startpos):
        self.actor_list.append(actor(g, startpos, 'crowd', rand(1,2), g.const_speed_npc))
    
    def add_pc(self, g, startpos):
        self.actor_pc = actor(g, startpos, 'pc', 0, g.const_speed_pc)
    
    def add_guard(self, g, startpos):
        self.actor_list.append(actor(g, startpos, 'guard', 3, g.const_speed_npc))
    
    def do_step(self, g):
        for cactor in self.actor_list:
            cactor.move_ai(g)
            
    def move_pc(self, g, disp):
        self.actor_pc.move(g, disp)
        
    def get_all_actors(self, g):
        '''returns all actors in the game
        this uses error checking to ensure that when the player character init
        requests a copy of the player character that doesn't exist,
        an empty array is returned instead'''
        try:
            return self.actor_list + [self.actor_pc]
        except AttributeError:
            return self.actor_list
    
    def tile_occupied(self, g, tile):
        '''returns true if tile (x,y) is occupied by another actor'''
        occupied_status = False
        for cactor in self.get_all_actors(g):
            if (cactor.pos_x,cactor.pos_y) == tile:
                occupied_status = True
        return occupied_status

class actor:
    '''any in-game person / animal'''
    
    def __init__(self, g, startpos, actor_type, ai_type, start_speed):
        '''set initial variables
        if startpos is None, find a random walkable tile
        actor types: pc, crowd, guard'''
        
        self.char = ord('@')
        self.type = actor_type
        if actor_type == 'pc':
            self.ispc = True
        else:
            self.ispc = False
            
        if self.ispc:
            self.col = libtcod.black
        elif self.type == 'crowd':
            self.col = g.const_crowd_colors[rand(0,len(g.const_crowd_colors)) - 1]
        elif self.type == 'guard':
            self.col = libtcod.dark_red
        else:
            self.col = libtcod.yellow
        self.ai_type = ai_type
        self.speed = start_speed
        self.speed_is_sprinting = False
        
        #move_dir used by ai type 2
        while True:
            self.ai_move_dir = (rand(-1,1), rand(-1,1))
            #continue to set a random walk direction until a non-stationary one is found
            if self.ai_move_dir != (0,0):
                break
        
        #the value of libtcod.sys_elapsed_milli() at the last player movement
        self.time_at_last_move = libtcod.sys_elapsed_milli()
        
        if startpos == None:
            if g.map.pc_start_pos != None and self.ispc:
                self.pos_x, self.pos_y = g.map.pc_start_pos
            else:
                #find a random position
                avaliable_tiles = alltiles(g.map.width, g.map.height)
                while len(avaliable_tiles) > 0: #while tiles are left
                    #get a random tile
                    cindex = rand(0, len(avaliable_tiles) - 1)
                    ctile = avaliable_tiles[cindex]
                    #if the tile is open
                    if g.map.land[ctile[1], ctile[0]].walkable and not g.actors.tile_occupied(g, ctile):
                        self.pos_x, self.pos_y = ctile
                        break
                    else:
                        #if tile not open, remove it from the list
                        avaliable_tiles = avaliable_tiles[:cindex] + avaliable_tiles[cindex + 1:]
                else:
                    #if no walkable tiles exist on the map, just pick a random tile
                    self.pos_x = rand(0, g.map.width - 1)
                    self.pos_y = rand(0, g.map.height - 1)
        else:
            self.pos_x, self.pos_y = startpos
        
        if self.ispc:
            self.center_map_on_actor(g)
        
    def move(self, g, disp):
        '''move the actor
        disp should be a tuple conaining the desired displacement
        eg: (1,-1) = move diagonally SE, (0,1) = move down
        each part should be in [-1,0,1]
        
        return codes:
        0 successfully moved
        1 collided with map / map edge
        2 collided with another actor
        3 movement too soon after previous movement (restricted by movement speed)'''
        
        targ_x = self.pos_x + disp[0]
        targ_y = self.pos_y + disp[1]
        
        move_status = 0
        move_allowed = True
        
        #check if enough time has passed since last movement
        cspeed = self.speed
        if self.speed_is_sprinting:
            cspeed /= g.const_speed_sprint_multiplier
        if libtcod.sys_elapsed_milli() - self.time_at_last_move < cspeed:
            move_allowed = False
            move_status = 3
        
        #check for map edges
        if targ_x < 0 or targ_y < 0 or targ_x >= g.map.width or targ_y >= g.map.height:
            move_allowed = False
            move_status = 1
        else:
        
            #check for walkability
            if not g.map.land[targ_y, targ_x].walkable:
                move_allowed = False
                move_status = 1
            
            #check for other actors in way
            if g.actors.tile_occupied(g, (targ_x, targ_y)):
                move_allowed = False
                move_status = 2
        
        #move the actor, if allowed
        if move_allowed:
            self.pos_x, self.pos_y = targ_x, targ_y
            self.time_at_last_move = libtcod.sys_elapsed_milli()
            
        #center the map if this is the player character    
        if self.ispc:
            self.center_map_on_actor(g)
        
        if self.ispc:
            if g.map.land[self.pos_y, self.pos_x].type == 'level door':
                g.load_area(g.map.land[self.pos_y, self.pos_x].target)
        
        return move_status
    
    def move_ai(self, g):
        '''ai types:
        0 no ai / stationary
        1 random walk
        2 random walk in straight lines
        3 guard'''
        if self.ai_type == 0:
            pass
        
        elif self.ai_type == 1:
            if rand(1,5) == 1:
                self.move(g, (rand(-1,1), rand(-1,1)))
            
        elif self.ai_type == 2:
                #move, and if a collision is found, move in a new direction
                if self.move(g, self.ai_move_dir) in (1, 2):
                    
                    while True:
                        self.ai_move_dir = (rand(-1,1), rand(-1,1))
                        #continue to set a random walk direction until a non-stationary one is found
                        if self.ai_move_dir != (0,0):
                            break
        
        elif self.ai_type == 3:
            pass
        #guard ai
        #TODO this

    def center_map_on_actor(self, g):
        g.scroll_x = self.pos_x - (g.window_width / 2)
        g.scroll_y = self.pos_y - (g.window_height / 2)
        #clamp the screen to the map area
        if g.scroll_x < 0:
            g.scroll_x = 0
        if g.scroll_y < 0:
            g.scroll_y = 0
        if g.scroll_x + g.window_width > g.map.width:
            g.scroll_x = g.map.width - g.window_width
        if g.scroll_y + g.window_height > g.map.height:
            g.scroll_y = g.map.height - g.window_height
    